package org.mimizet.codegen;

import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.visitor.VoidVisitorAdapter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.logging.Log;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
 * User: Admin
 * Date: 29.08.11
 * Time: 22:41
 */
@SuppressWarnings({"ALL"})
public class XstreamCodegen {
    public static final org.slf4j.Logger logger = LoggerFactory.getLogger(Mimizet.class);

    private Log log;
    String cacheDir;
    String outputDir;

    public XstreamCodegen(Log log, String cacheDir, String outputDir) {
        this.log = log;
        this.cacheDir = cacheDir;
        this.outputDir = outputDir;

        File file;

        file = new File(cacheDir);
        if(!file.exists()) file.mkdirs();

        file = new File(outputDir);
        if(!file.exists()) file.mkdirs();
    }

    public void saveAspect(File javaSourceFile, final Mimizet.Class aClass) throws Exception {
        Map<String, String> map = generate(javaSourceFile, aClass);

        File packageDir = new File(String.format("%s/%s", outputDir, map.get("PACKAGE_PATH")));

        if(!packageDir.exists()){
            packageDir.mkdirs();
        }

        File aspectFile = new File(packageDir, map.get("ASPECT_CLASS") + ".aj");

        log.info("writing aspect to file " + aspectFile);
        FileUtils.writeStringToFile(aspectFile, map.get("SOURCE"));
    }

    public Map<String, String> generate(File javaSourceFile, final Mimizet.Class aClass) throws Exception {
        CompilationUnit unit = JavaParser.parse(javaSourceFile);

        final Map<String, String> variables = new HashMap<String, String>();

        String packageName = unit.getPackage().getName().toString();
        String className = StringUtils.substringAfterLast(aClass.name, ".");

        variables.put("PACKAGE_NAME", packageName);
        variables.put("CLASS_NAME", className);

        variables.put("PACKAGE_PATH",  StringUtils.replaceChars(packageName, ".", "/"));
        variables.put("ASPECT_CLASS", className + "Aspect");
        variables.put("CACHE_DIR", StringUtils.replaceChars(cacheDir, '\\', '/'));

        final SimpleTemplate pointcutsTemplate = new SimpleTemplate();

        final ArrayList<MethodDeclaration> methodDeclarations = new ArrayList<MethodDeclaration>();

        new VoidVisitorAdapter() {
            public void visit(MethodDeclaration methodDeclaration, Object arg) {
                methodDeclarations.add(methodDeclaration);
            }
        }.visit(unit, null);

        for (Mimizet.Method method : aClass.methods) {
            //TODO: update import section with arguments types and return types
            if (method.findMethod(methodDeclarations)) {
                variables.put("POINTCUT", "pointcut_" + method.name);
                variables.put("METHOD_RETURNS", method.returns);
                variables.put("METHOD_NAME", method.name);
                variables.put("METHOD_PARAMETERS", StringUtils.join(method.arguments.toArray(), ", "));

                variables.put("CONVERTER", StringUtils.isBlank(method.converter) ? "" : ", \"" + method.converter + "\"");

                pointcutsTemplate.concatenate(ADVICE_TEXT, variables);
            } else {
                throw new RuntimeException(String.format("Method not found: %s.%s(%s)",
                        aClass.name,
                        method.name,
                        (method.arguments.size() > 0 ? method.arguments.toString() : "")));
            }
        }

        final SimpleTemplate template = new SimpleTemplate().concatenate(HEADER, variables);

        template.sb.append(pointcutsTemplate.sb).append(FOOTER);

        variables.put("SOURCE", template.sb.toString());

        return variables;
    }

    class SimpleTemplate{
        StringBuilder sb = new StringBuilder(8192);

        public SimpleTemplate concatenate(String template, Map<String, String> vars){
            String[] keys = new String[vars.size()];
            String[] replacements = new String[vars.size()];

            int i = 0;

            for (Map.Entry<String, String> entry : vars.entrySet()) {
                keys[i] = new StringBuilder().append("#{").append(entry.getKey()).append("}").toString();
                replacements[i++] = entry.getValue();
            }

            template = StringUtils.replaceEachRepeatedly(template, keys, replacements);

            sb.append(template);

            return this;
        }
    }

    public static void main(String[] args) throws Exception {
       /* new XstreamCodegen("mimicache", "./src/main/java").saveAspect(
                new File("./src/main/java/org/mimizet/test/A.java"),
                Mimizet.parseXml(new File("mimi.xml")).classes.get(0)
        );*/
    }

    public static final String HEADER =
            "package #{PACKAGE_NAME};\n" +
            "\n" +
            "import java.io.File;\n" +
            "import java.util.Iterator;\n" +
            "\n" +
            "import org.aspectj.lang.JoinPoint;\n" +
            "import org.mimizet.*;\n" +
            "\n" +
            "public aspect #{ASPECT_CLASS} {\n" +
            "\tStorage storage = new FSStorage(new XStreamSerializer(), new File(\"#{CACHE_DIR}\"));\n" +
            "\tMethodCallFactory methodCallFactory = new DefaultMethodCallFactory();\n" +
            "\t\n" +
            "\tprivate MethodCall getMethodCall(JoinPoint joinPoint) {\n" +
            "\t\treturn getMethodCall(joinPoint, \"\");\n" +
            "\t}\n" +
            "\t\n" +
            "\tprivate MethodCall getMethodCall(JoinPoint joinPoint, String string) {\n" +
            "\t\tMethodCall methodCall = methodCallFactory.create(\n" +
            "\t\t\t\tjoinPoint.getTarget().getClass().getName(), \n" +
            "\t\t\t\tjoinPoint.toString(), \n" +
            "\t\t\t\tjoinPoint.getArgs());\n" +
            "\t\t\n" +
            "\t\tmethodCall.resultConverterClass = string;\n" +
            "\t\t\n" +
            "\t\tstorage.findMethodCall(methodCall);\n" +
            "\n" +
            "\t\treturn methodCall;\n" +
            "\t}\n";

    public static final String ADVICE_TEXT = "\n" +
            "\tpointcut #{POINTCUT}() : call(#{METHOD_RETURNS} #{CLASS_NAME}.#{METHOD_NAME}(#{METHOD_PARAMETERS}));\n" +
            "\t#{METHOD_RETURNS} around() : #{POINTCUT}() && !within(#{ASPECT_CLASS} +) {\t\t\n" +
            "\t\tMethodCall methodCall = getMethodCall(thisJoinPoint#{CONVERTER});\n" +
            "\t\t\n" +
            "\t\tif(methodCall.result == null){\n" +
            "\t\t\tmethodCall.result = proceed();\n" +
            "\t\t\tstorage.store(methodCall);\n" +
            "\t\t}\n" +
            "\t\t\n" +
            "\t\treturn (#{METHOD_RETURNS}) methodCall.result;\n" +
            "\t}\n" +
            "\t\n";

    public static final String FOOTER = "}";
}
